Kontrolyor
===========

Kontrolyor [MVC](https://ru.wikipedia.org/wiki/Model-View-Controller) arxitekturasining bir qismi hisoblanadi. Bu [[yii\base\Controller]] klassidan voris qilib yaratilgan klass obyektlaridir, ushbu obyektlar so'rovlarni (request) qayta ishlash va javoblarni (response) tayyorlash uchun mo'ljallangan. [Ilova (application)](structure-applications.md) so'rovlarni qayta ishlagandan so'ng, so'rovlarga mos kontrolyor obyektlarini yaratadi va bu obyektlar kiruvchi ma'lumotlarni tahlil qiladi hamda ushbu ma'lumotlarni [model](structure-models.md)ga jo'natadi so'ng modeldan olingan natijalarni [namoyish (view)](structure-views.md)ga joylashtiradi va oxirgi natijani hosil qiladi.

## Amallar <span id="actions"></span>

Kontrolyorlar *amallar (action)*dan tashkil topadi, *amallar* asosiy bloklar hisoblanib, foydalanuvchilar amallarga murojaat qilishi mumkin va ma'lum buyruqlar bajarilib natija hosil qilinadi hamda natija foydalanuvchiga qaytariladi. Bitta kontrolyorda bitta va undan ortiq amal bo'lishi mumkin.

Quyidagi misolda ikkita amaldan (`view` va `create`) tashkil topgan `post` kontrolyori keltirilgan:

```php
namespace app\controllers;

use Yii;
use app\models\Post;
use yii\web\Controller;
use yii\web\NotFoundHttpException;

class PostController extends Controller
{
    public function actionView($id)
    {
        $model = Post::findOne($id);
        if ($model === null) {
            throw new NotFoundHttpException;
        }

        return $this->render('view', [
            'model' => $model,
        ]);
    }

    public function actionCreate()
    {
        $model = new Post;

        if ($model->load(Yii::$app->request->post()) && $model->save()) {
            return $this->redirect(['view', 'id' => $model->id]);
        } else {
            return $this->render('create', [
                'model' => $model,
            ]);
        }
    }
}
```


В действии `view` (определенном методом `actionView()`), код сначала загружает [модель](structure-models.md)
согласно запрошенному ID модели; Если модель успешно загружена, то код отобразит ее с помощью [представления](structure-views.md)
под названием `view`. В противном случае будет брошено исключение.

В действии `create` (определенном методом `actionCreate()`), код аналогичен. Он сначала пытается загрузить [модель](structure-models.md)
с помощью данных из запроса и сохранить модель. Если все прошло успешно, то код перенаправляет браузер на действие `view` с ID только
что созданной модели. В противном случае он отобразит представление `create`, через которое пользователь может заполнить нужные данные.


## Маршруты <span id="routes"></span>

Конечные пользователи обращаются к действиям через так называемые *маршруты*. Маршрут это строка, состоящая из следующих частей:

* ID модуля: он существует, только если контроллер принадлежит не приложению, а [модулю](structure-modules.md);
* ID контроллера: строка, которая уникально идентифицирует контроллер среди всех других контроллеров одного и того же приложения
  (или одного и того же модуля, если контроллер принадлежит модулю);
* ID действия: строка, которая уникально идентифицирует действие среди всех других действия одного и того же контроллера.

Маршруты могут иметь следующий формат:

```
ControllerID/ActionID
```

или следующий формат, если контроллер принадлежит модулю:

```php
ModuleID/ControllerID/ActionID
```

Таким образом, если пользователь запрашивает URL `https://hostname/index.php?r=site/index`, то `index` действие в `site` контроллере будет вызвано.
Секция [Маршрутизация](runtime-routing.md) содержит более подробную информацию о том как маршруты сопоставляются с действиями.


## Создание контроллеров <span id="creating-controllers"></span>

В [[yii\web\Application|Веб приложениях]], контроллеры должны быть унаследованы от [[yii\web\Controller]] или его потомков.
Аналогично для [[yii\console\Application|консольных приложений]], контроллеры должны быть унаследованы от [[yii\console\Controller]] или
его потомков. Следующий код определяет `site` контроллер:

```php
namespace app\controllers;

use yii\web\Controller;

class SiteController extends Controller
{
}
```


### ID контроллеров <span id="controller-ids"></span>

Обычно контроллер сделан таким образом, что он должен обрабатывать запросы, связанные с определенным ресурсом.
Именно по этим причинам, ID контроллеров обычно являются существительные, ссылающиеся на ресурс, который они обрабатывают.
Например, вы можете использовать `article` в качестве ID контроллера, которые отвечает за обработку данных статей.

По-умолчанию, ID контроллеров должны содержать только следующие символы: Английские буквы в нижнем регистре, цифры, подчеркивания,
тире и слэш. Например, оба `article` и `post-comment` являются допустимыми ID контроллеров, в то время как `article?`, `PostComment`,
`admin\post` не являются таковыми.

ID контроллеров также могут содержать префикс подпапки. Например, в `admin/article` часть `article` является контроллером в 
подпапке `admin` в [[yii\base\Application::controllerNamespace|пространстве имен контроллеров]].
Допустимыми символами для префиксов подпапок являются: Английские буквы в нижнем  и верхнем регистре, символы подчеркивания и
слэш, где слэш используется в качестве разграничителя для многовложенных подпапок (например `panels/admin`).



### Правила наименования классов контроллеров <span id="controller-class-naming"></span>

Названия классов контроллеров могут быть получены из ID контроллеров следующими способами:

* Привести в верхний регистр первый символ в каждом слове, разделенном дефисами. Обратите внимание что, если ID контроллера
  содержит слэш, то данное правило распространяется только на часть после последнего слэша в ID контроллера;
* Убрать дефисы и заменить любой прямой слэш на обратный;
* Добавить суффикс `Controller`;
* Добавить в начало [[yii\base\Application::controllerNamespace|пространство имен контроллеров]].

Ниже приведены несколько примеров, с учетом того, что [[yii\base\Application::controllerNamespace|пространство имен контроллеров]]
имеет значение по умолчанию равное `app\controllers`:

* `article` соответствует `app\controllers\ArticleController`;
* `post-comment` соответствует `app\controllers\PostCommentController`;
* `admin/post-comment` соответствует `app\controllers\admin\PostCommentController`;
* `adminPanels/post-comment` соответствует `app\controllers\adminPanels\PostCommentController`.

Классы контроллеров должны быть [автозагружаемыми](concept-autoloading.md). Именно по этой причине, в вышеприведенном примере,
контроллер `article` должен быть сохранен в файл, [псевдоним](concept-aliases.md) которого `@app/controllers/ArticleController.php`;
в то время как контроллер `admin/post-comment` должен находиться в файле `@app/controllers/admin/PostCommentController.php`.

> Информация: Последний пример `admin/post-comment` показывает каким образом вы можете расположить контроллер в подпапке
  [[yii\base\Application::controllerNamespace|пространства имен контроллеров]]. Это очень удобно, когда вы хотите организовать свои контроллеры
  в несколько категорий и не хотите использовать [модули](structure-modules.md).


### Карта контроллеров <span id="controller-map"></span>

Вы можете сконфигурировать [[yii\base\Application::controllerMap|карту контроллеров]] для того, чтобы преодолеть
описанные выше ограничения именования ID контроллеров и названий классов. В основном это очень удобно, когда вы используете
сторонние контроллеры, именование которых вы не можете контролировать.

Вы можете сконфигурировать [[yii\base\Application::controllerMap|карту контроллеров]] в [настройках приложения](structure-applications.md#application-configurations)
следующим образом:

```php
[
    'controllerMap' => [
        [
            // объявляет "account" контроллер, используя название класса
            'account' => 'app\controllers\UserController',

            // объявляет "article" контроллер, используя массив конфигурации
            'article' => [
                'class' => 'app\controllers\PostController',
                'enableCsrfValidation' => false,
            ],
        ],
    ],
]
```

### Контроллер по умолчанию <span id="default-controller"></span>

Каждое приложение имеет контроллер по умолчанию, указанный через свойство [[yii\base\Application::defaultRoute]].
Когда в запросе не указан [маршрут](#routes), тогда будет использован маршрут указанный в данном свойстве.
Для [[yii\web\Application|Веб приложений]], это значение `'site'`, в то время как для [[yii\console\Application|консольных приложений]],
это `'help'`. Таким образом, если задан URL `https://hostname/index.php`, это означает, что контроллер `site` выполнит обработку запроса.

Вы можете изменить контроллер по умолчанию следующим образом в [настройках приложения](structure-applications.md#application-configurations):

```php
[
    'defaultRoute' => 'main',
]
```


## Создание действий <span id="creating-actions"></span>

Создание действий не представляет сложностей также как и объявление так называемых *методов действий* в классе контроллера. Метод действия
это *public* метод, имя которого начинается со слова `action`. Возвращаемое значение метода действия представляет собой ответные данные,
которые будут высланы конечному пользователю. Приведенный ниже код определяет два действия `index` и `hello-world`:

```php
namespace app\controllers;

use yii\web\Controller;

class SiteController extends Controller
{
    public function actionIndex()
    {
        return $this->render('index');
    }

    public function actionHelloWorld()
    {
        return 'Hello World';
    }
}
```


### ID действий <span id="action-ids"></span>

В основном действие разрабатывается для какой-либо конкретной обработки ресурса. По этой причине, ID действий в основном
являются глаголами, такими как `view`, `update`, и т. д.

По-умолчанию, ID действия должен содержать только следующие символы: Английские буквы в нижнем регистре, цифры,
подчеркивания и дефисы. Дефисы в ID действий используются для разделения слов. Например, `view`, `update2`, `comment-post` являются
допустимыми ID действий, в то время как `view?`, `Update` не являются таковыми.

Вы можете создавать действия двумя способами: встроенные действия и отдельные действия. Встроенное действие является методом, определенным
в классе контроллера, в то время как отдельное действие является экземпляром класса, унаследованного от [[yii\base\Action]] или его потомков.
Встроенные действия требуют меньше усилий для создания и в основном используются если у вас нет надобности в повторном использовании действий.
Отдельные действия, с другой стороны, в основном создаются для использования в различных контроллерах или при использовании в [расширениях](structure-extensions.md).


### Встроенные действия <span id="inline-actions"></span>

Встроенные действия это те действия, которые определены в рамках методов контроллера, как мы это уже обсудили.

Названия методов действий могут быть получены из ID действий следующим образом:

* Привести первый символ каждого слова в ID действия в верхний регистр;
* Убрать дефисы;
* Добавить префикс `action`.

Например, `index` соответствует `actionIndex`, а `hello-world` соответствует `actionHelloWorld`.

> Примечание: Названия имен действий являются *регистрозависимыми*. Если у вас есть метод `ActionIndex`, он не будет
  учтен как метод действия, таким образом, запрос к действию `index` приведет к выбросу исключению. Также следует учесть, что методы действий
  должны иметь область видимости public. Методы имеющие область видимости private или protected НЕ определяют методы встроенных действий.


Встроенные действия в основном используются, потому что для их создания не нужного много усилий. Тем не менее, если вы планируете повторно
использовать некоторые действия в различных местах, или если вы хотите перераспределить действия, вы должны определить его как *отдельной действие*.


### Отдельные действия <span id="standalone-actions"></span>

Отдельные действия определяются в качестве классов, унаследованных от [[yii\base\Action]] или его потомков.
Например, в Yii релизах, присутствуют [[yii\web\ViewAction]] и [[yii\web\ErrorAction]], оба из которых являются
отдельными действиями.

Для использования отдельного действия, вы должны указать его в *карте действий*, с помощью переопределения метода
[[yii\base\Controller::actions()]] в вашем классе контроллера, следующим образом:

```php
public function actions()
{
    return [
        // объявляет "error" действие с помощью названия класса
        'error' => 'yii\web\ErrorAction',

        // объявляет "view" действие с помощью конфигурационного массива
        'view' => [
            'class' => 'yii\web\ViewAction',
            'viewPrefix' => '',
        ],
    ];
}
```

Как вы можете видеть, метод `actions()` должен вернуть массив, ключами которого являются ID действий, а значениями - соответствующие
названия класса действия или [конфигурация](concept-configurations.md). В отличие от встроенных действий, ID отдельных действий могут
содержать произвольные символы, до тех пор пока они определены в методе `actions()`.

Для создания отдельного действия, вы должны наследоваться от класса [[yii\base\Action]] или его потомков, и реализовать
метод `run()` с областью видимости public. Роль метода `run()` аналогична другим методам действий. Например,

```php
<?php
namespace app\components;

use yii\base\Action;

class HelloWorldAction extends Action
{
    public function run()
    {
        return "Hello World";
    }
}
```


### Результаты действий <span id="action-results"></span>

Возвращаемое значение методов действий или метода `run()` отдельного действия очень важно. Оно является результатом
выполнения соответствующего действия.

Возвращаемое значение может быть объектом [response](runtime-responses.md), который будет отослан конечному пользователю 
в качестве ответа.

* Для [[yii\web\Application|Веб приложений]], возвращаемое значение также может быть произвольными данными, которые будут
  присвоены [[yii\web\Response::data]], а затем сконвертированы в строку, представляющую тело ответа.
* Для [[yii\console\Application|Консольных приложений]], возвращаемое значение также может быть числом, представляющим
  [[yii\console\Response::exitStatus|статус выхода]] исполнения команды.

В вышеприведенных примерах, все результаты действий являются строками, которые будут использованы в качестве тела ответа,
высланного пользователю. Следующий пример, показывает действие может перенаправить браузер пользователя на новый URL, с помощью
возврата response объекта (т. к. [[yii\web\Controller::redirect()|redirect()]] метод возвращает response объект):

```php
public function actionForward()
{
    // перенаправляем браузер пользователя на https://example.com
    return $this->redirect('https://example.com');
}
```


### Параметры действий <span id="action-parameters"></span>

Методы действий для встроенных действий и методы `run()` для отдельных действий могут принимать параметры,
называемые *параметры действий*. Их значения берутся из запросов. Для [[yii\web\Application|Веб приложений]],
значение каждого из параметров действия берется из `$_GET`, используя название параметра в качестве ключа;
для [[yii\console\Application|консольных приложений]], они соответствуют аргументам командной строки.

В приведенном ниже примере, действие `view` (встроенное действие) определяет два параметра: `$id` и `$version`.

```php
namespace app\controllers;

use yii\web\Controller;

class PostController extends Controller
{
    public function actionView($id, $version = null)
    {
        // ...
    }
}
```

Для разных запросов параметры действий будут определены следующим образом:

* `https://hostname/index.php?r=post/view&id=123`: параметр `$id` будет присвоено значение `'123'`, в то время
  как `$version` будет иметь значение `null`, т. к. строка запроса не содержит параметра `version`;
* `https://hostname/index.php?r=post/view&id=123&version=2`: параметрам `$id` и `$version` будут присвоены
  значения `'123'` и `'2'` соответственно;
* `https://hostname/index.php?r=post/view`: будет брошено исключение [[yii\web\BadRequestHttpException]], т. к.
  обязательный параметр `$id` не был указан в запросе;
* `https://hostname/index.php?r=post/view&id[]=123`: будет брошено исключение [[yii\web\BadRequestHttpException]], т. к.
  параметр `$id` получил неверное значение `['123']`.

Если вы хотите, чтобы параметр действия принимал массив значений, вы должны использовать type-hint значение `array`, как показано ниже:

```php
public function actionView(array $id, $version = null)
{
    // ...
}
```

Теперь, если запрос будет содержать URL `https://hostname/index.php?r=post/view&id[]=123`, то параметр `$id` примет значение
`['123']`. Если запрос будет содержать URL `https://hostname/index.php?r=post/view&id=123`, то параметр `$id` все равно будет
содержать массив, т. к. скалярное значение `'123'` будет автоматически сконвертировано в массив.

Вышеприведенные примеры в основном показывают как параметры действий работают для Веб приложений. Больше информации
о параметрах консольных приложений представлено в секции [Консольные команды](tutorial-console.md).


### Действие по умолчанию <span id="default-action"></span>

Каждый контроллер имеет действие, указанное через свойство [[yii\base\Controller::defaultAction]].
Когда [маршрут](#routes) содержит только ID контроллера, то подразумевается, что действие контроллера по умолчанию
было запрошено.

По-умолчанию, это действие имеет значение `index`. Если вы хотите изменить это значение, просто переопределите данное
свойство в классе контроллера следующим образом:

```php
namespace app\controllers;

use yii\web\Controller;

class SiteController extends Controller
{
    public $defaultAction = 'home';

    public function actionHome()
    {
        return $this->render('home');
    }
}
```


## Жизненный цикл контроллера <span id="controller-lifecycle"></span>

При обработке запроса, [приложение](structure-applications.md) создаст контроллер, основываясь на 
запрошенном [маршруте](#routes). Для выполнения запроса, контроллер пройдет через следующие этапы
жизненного цикла:

1. Метод [[yii\base\Controller::init()]] будет вызван после того как контроллер будет создан и сконфигурирован;
2. Контроллер создает объект действия, основываясь на запрошенном ID действия:
   * Если ID действия не указан, то будет использовано [[yii\base\Controller::defaultAction|ID действия по умолчанию]];
   * Если ID действия найдено в [[yii\base\Controller::actions()|карте действий]], то отдельное действие будет создано;
   * Если ID действия соответствует методу действия, то встроенное действие будет создано;
   * В противном случае, будет выброшено исключение [[yii\base\InvalidRouteException]].
3. Контроллер последовательно вызывает метод `beforeAction()` приложения, модуля (если контроллер принадлежит модулю) и
   самого контроллера.
   * Если один из методов вернул `false`, то остальные, не вызванные методы `beforeAction` будут пропущены, а выполнение
     действия будет отменено;
   * По-умолчанию, каждый вызов метода `beforeAction()` вызовет событие `beforeAction`, на которое вы можете назначить обработчики.
4. Контроллер запускает действие:
   * Параметры действия будут проанализированы и заполнены из данных запроса.
5. Контроллер последовательно вызывает методы `afterAction` контроллера, модуля (если контроллер принадлежит модулю) и приложения.
   * По-умолчанию, каждый вызов метода `afterAction()` вызовет событие `afterAction`, на которое вы можете назначить обработчики.
6. Приложение, получив результат выполнения действия, присвоит его объекту [response](runtime-responses.md).


## Лучшие практики <span id="best-practices"></span>

В хорошо организованных приложениях контроллеры обычно очень тонкие и содержат лишь несколько строк кода.
Если ваш контроллер слишком сложный, то обычно это означает, что вам надо провести его рефакторинг и перенести часть кода
в другие места.

В целом, контроллеры

* могут иметь доступ к данным [запроса](runtime-requests.md);
* могут вызывать методы [моделей](structure-models.md) и других компонентов системы с данными запроса;
* могут использовать [представления](structure-views.md) для формирования ответа;
* не должны заниматься обработкой данных, это должно происходить в [слое моделей](structure-models.md);
* должны избегать использования HTML или другой разметки, лучше это делать в [представлениях](structure-views.md).
